//
// 3D Gallery for Computer Graphics course
//
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <cmath>
#include <string>

#ifdef __APPLE__
#  include <GLUT/glut.h>
#else
#  include <GL/glew.h>
#  include <GL/glut.h>
#  include <GL/glext.h>
#pragma comment(lib, "glew32.lib")
#endif

#include <gl/glaux.h>

using namespace std;

#define MIN_DISTANCE 3.0f
#define W 60.0f
#define H 10.0f
#define L 80.0f
#define WINDOW_H 600
#define WINDOW_W 1000
#define PI 3.141592f
#define PAINTING_THICKNESS 1
#define NUMBER_TEXTURE 32
#define NUMBER_PAINTINGS (NUMBER_TEXTURE-4)

float now_x = 0.0;
float now_y =0.0;
float now_z = 0.0;
float rotate_angle = 180.0;

// painings file name
static const char *paintings[28] = 
{
	"Textures/1.bmp",
	"Textures/2.bmp",
	"Textures/3.bmp",
	"Textures/4.bmp",
	"Textures/5.bmp",
	"Textures/6.bmp",
	"Textures/7.bmp",
	"Textures/8.bmp",
	"Textures/9.bmp",
	"Textures/10.bmp",
	"Textures/11.bmp",
	"Textures/12.bmp",
	"Textures/13.bmp",
	"Textures/14.bmp",
	"Textures/15.bmp",
	"Textures/16.bmp",
	"Textures/17.bmp",
	"Textures/18.bmp",
	"Textures/19.bmp",
	"Textures/20.bmp",
	"Textures/21.bmp",
	"Textures/22.bmp",
	"Textures/23.bmp",
	"Textures/24.bmp",
	"Textures/25.bmp",
	"Textures/26.bmp",
	"Textures/27.bmp",
	"Textures/28.bmp",
};

static const char *WALL_FILE = "Textures/wall_3.bmp";//"Textures/white_wall_quard.bmp";
static const char *FLOOR_FILE = "Textures/floor_piece_new.bmp";
static const char *ROOF_FILE = "Textures/roof_piece.bmp";
static const char *LIGHT_FILE = "Textures/light_e.bmp";

// array of textures
static unsigned int texture[NUMBER_TEXTURE];

// Struct of bitmap file.
struct BitMapFile
{
	int sizeX;
	int sizeY;
	unsigned char *data;
};

// Routine to read a bitmap file.
// Works only for uncompressed bmp files of 24-bit color.
BitMapFile *getBMPData(string filename)
{
	BitMapFile *bmp = new BitMapFile;
	unsigned int size, offset, headerSize;

	// Read input file name.
	ifstream infile(filename.c_str(), ios::binary);

	// Get the starting point of the image data.
	infile.seekg(10);
	infile.read((char *) &offset, 4); 

	// Get the header size of the bitmap.
	infile.read((char *) &headerSize,4);

	// Get width and height values in the bitmap header.
	infile.seekg(18);
	infile.read( (char *) &bmp->sizeX, 4);
	infile.read( (char *) &bmp->sizeY, 4);

	// Allocate buffer for the image.
	size = bmp->sizeX * bmp->sizeY * 24;
	bmp->data = new unsigned char[size];

	// Read bitmap data.
	infile.seekg(offset);
	infile.read((char *) bmp->data , size);

	// Reverse color from bgr to rgb.
	int temp;
	for (int i = 0; i < size; i += 3)
	{ 
		temp = bmp->data[i];
		bmp->data[i] = bmp->data[i+2];
		bmp->data[i+2] = temp;
	}

	return bmp;
}

void loadSingleTexture(const char *filename, int index)
{
	BitMapFile *image = getBMPData(filename);
	glBindTexture(GL_TEXTURE_2D, texture[index]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->sizeX, image->sizeY, 0, 
		GL_RGB, GL_UNSIGNED_BYTE, image->data);
}

void loadRepeatTexture(const char *filename, int index)
{
	BitMapFile *image = getBMPData(filename);
	glBindTexture(GL_TEXTURE_2D, texture[index]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->sizeX, image->sizeY, 0, 
		GL_RGB, GL_UNSIGNED_BYTE, image->data);
}

void loadExternalTextures() 
{
	// 0: the wall piece texture
	loadSingleTexture(WALL_FILE, 0);
	// 1: floor piece texture
	loadRepeatTexture(FLOOR_FILE, 1);
	// 2: roof piece texture
	loadRepeatTexture(ROOF_FILE, 2);
	// 3: light map file
	loadSingleTexture(LIGHT_FILE, 3);
	// other, paiting file
	for (int i = 0; i < NUMBER_PAINTINGS; i++) {
		loadSingleTexture(paintings[i], 4 + i);
	}
}

// Initialization routine.
void initialize(void)
{
	glClearColor(0.0, 0.0, 0.0, 1.0);

	// Create texture index array.
	glGenTextures(NUMBER_TEXTURE, texture);

	// Load external textures.
	loadExternalTextures();

	// Turn on OpenGL texturing.
	glEnable(GL_TEXTURE_2D); 

	// Specify how texture values combine with current surface color values.
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
}

// Drawing routine.
void drawScene(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(30.0, (double)WINDOW_W / (double)WINDOW_H, 0.1f, 200.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	float fR = 360.0 - ( rotate_angle + 90.0f );
	gluLookAt(
		now_x, 0.0f, now_z,
		now_x + 10.0f * cosf(fR * PI / 180.0f), 0.0f, now_z + 10.0f * sinf(fR * PI / 180.0f),
		0.0f, 1.0f, 0.0f); 
	
	glPushMatrix();

	// draw floor
	glBindTexture(GL_TEXTURE_2D, texture[1]);
	glBegin(GL_POLYGON);
		glTexCoord2f(0.0, 0.0); glVertex3f(-W/2, -H/2, -L/2);
		glTexCoord2f(16.0, 0.0); glVertex3f(W/2, -H/2, -L/2);
		glTexCoord2f(16.0, 16.0); glVertex3f(W/2, -H/2, L/2);
		glTexCoord2f(0.0, 16.0); glVertex3f(-W/2, -H/2, L/2);
	glEnd();

	// draw roof
	glBindTexture(GL_TEXTURE_2D, texture[2]);
	glBegin(GL_POLYGON);
		glTexCoord2f(0.0, 0.0); glVertex3f(-W/2, H/2, -L/2);
		glTexCoord2f(16.0, 0.0); glVertex3f(W/2, H/2, -L/2);
		glTexCoord2f(16.0, 16.0); glVertex3f(W/2, H/2, L/2);
		glTexCoord2f(0.0, 16.0); glVertex3f(-W/2, H/2, L/2);
	glEnd();

	// draw right wall
	for (int i = 0; i < (int)(L / H); i++) {
		// light map
		glBindTexture(GL_TEXTURE_2D, texture[3]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(W/2, -H/2, L/2 - i * H);
			glTexCoord2f(1, 0); glVertex3f(W/2, -H/2, L/2 - (i + 1) * H);
			glTexCoord2f(1, 1); glVertex3f(W/2, H/2, L/2 - (i + 1) * H);
			glTexCoord2f(0, 1); glVertex3f(W/2, H/2, L/2 - i * H);
		glEnd();
	
		// enable blending
		glEnable(GL_BLEND);

		// set blend function to multiply SRC & DST colours
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);

		// show wall
		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glBegin(GL_POLYGON);
			glTexCoord2f(0, 0); glVertex3f(W/2, -H/2, L/2 - i * H);
			glTexCoord2f(1, 0); glVertex3f(W/2, -H/2, L/2 - (i + 1) * H);
			glTexCoord2f(1, 1); glVertex3f(W/2, H/2, L/2 - (i + 1) * H);
			glTexCoord2f(0, 1); glVertex3f(W/2, H/2, L/2 - i * H);
		glEnd();

		// disable blending
		glDisable(GL_BLEND);

		// paintings
		glBindTexture(GL_TEXTURE_2D, texture[i+4]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(W/2, -H/2 + H / 4, L/2 - i * H - H / 5);
			glTexCoord2f(1, 0); glVertex3f(W/2, -H/2 + H / 4, L/2 - (i + 1) * H + H / 5);
			glTexCoord2f(1, 1); glVertex3f(W/2, H/2 - H / 4, L/2 - (i + 1) * H + H / 5);
			glTexCoord2f(0, 1); glVertex3f(W/2, H/2 - H / 4, L/2 - i * H - H / 5);
		glEnd();
	}

	
	// left wall
	for (int i = 0; i < (int)(L / H); i++) {
		// light map
		glBindTexture(GL_TEXTURE_2D, texture[3]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2, -H/2, L/2 - i * H);
			glTexCoord2f(1, 0); glVertex3f(-W/2, -H/2, L/2 - (i + 1) * H);
			glTexCoord2f(1, 1); glVertex3f(-W/2, H/2, L/2 - (i + 1) * H);
			glTexCoord2f(0, 1); glVertex3f(-W/2, H/2, L/2 - i * H);
		glEnd();

	
		// enable blending
		glEnable(GL_BLEND);

		// set blend function to multiply SRC & DST colours
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);

		// show wall
		glBindTexture(GL_TEXTURE_2D, texture[0]);        
		glBegin(GL_POLYGON);
			glTexCoord2f(0, 0); glVertex3f(-W/2, -H/2, L/2 - i * H);
			glTexCoord2f(1, 0); glVertex3f(-W/2, -H/2, L/2 - (i + 1) * H);
			glTexCoord2f(1, 1); glVertex3f(-W/2, H/2, L/2 - (i + 1) * H);
			glTexCoord2f(0, 1); glVertex3f(-W/2, H/2, L/2 - i * H);
		glEnd();

		// disable blending
		glDisable(GL_BLEND);

		// paintings
		glBindTexture(GL_TEXTURE_2D, texture[i+12]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2, -H/2 + H / 4, L/2 - i * H - H / 5);
			glTexCoord2f(1, 0); glVertex3f(-W/2, -H/2 + H / 4, L/2 - (i + 1) * H + H / 5);
			glTexCoord2f(1, 1); glVertex3f(-W/2, H/2 - H / 4, L/2 - (i + 1) * H + H / 5);
			glTexCoord2f(0, 1); glVertex3f(-W/2, H/2 - H / 4, L/2 - i * H - H / 5);
		glEnd();
	}

	// front wall
	for (int i = 0; i < (int)(W / H); i++) {
		// light map
		glBindTexture(GL_TEXTURE_2D, texture[3]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i, -H/2, L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1), -H/2, L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1), H/2, L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i, H/2, L/2);
		glEnd();
	
		// enable blending
		glEnable(GL_BLEND);

		// set blend function to multiply SRC & DST colours
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);

		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glBegin(GL_POLYGON);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i, -H/2, L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1), -H/2, L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1), H/2, L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i, H/2, L/2);
		glEnd();

		// disable blending
		glDisable(GL_BLEND);

		// paintings
		glBindTexture(GL_TEXTURE_2D, texture[i+20]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i + H / 5, -H/2 + H / 4, L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1) - H / 5, -H/2 + H / 4, L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1) - H / 5, H/2 - H / 4, L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i + H / 5, H/2 - H / 4, L/2);
		glEnd();
	}

	// back wall
	for (int i = 0; i < (int)(W / H); i++) {
		// light map
		glBindTexture(GL_TEXTURE_2D, texture[3]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i, -H/2, -L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1), -H/2, -L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1), H/2, -L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i, H/2, -L/2);
		glEnd();
	
		// enable blending
		glEnable(GL_BLEND);

		// set blend function to multiply SRC & DST colours
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);

		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glBegin(GL_POLYGON);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i, -H/2, -L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1), -H/2, -L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1), H/2, -L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i, H/2, -L/2);
		glEnd();

		// disable blending
		glDisable(GL_BLEND);

		// paintings
		glBindTexture(GL_TEXTURE_2D, texture[i+26]);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0); glVertex3f(-W/2 + H*i + H / 5, -H/2 + H / 4, -L/2);
			glTexCoord2f(1, 0); glVertex3f(-W/2 + H*(i+1) - H / 5, -H/2 + H / 4, -L/2);
			glTexCoord2f(1, 1); glVertex3f(-W/2 + H*(i+1) - H / 5, H/2 - H / 4, -L/2);
			glTexCoord2f(0, 1); glVertex3f(-W/2 + H*i + H / 5, H/2 - H / 4, -L/2);
		glEnd();
	}

	glPopMatrix();

	glFlush();
}

// window resize
void resize(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30.0, (float)w/(float)h, 0.1f, 90.0);
	glMatrixMode(GL_MODELVIEW);
}

void specialkey(int key, int x, int y)
{
	float old_x = now_x;
	float old_z = now_z;

	// move forward
	if( key == GLUT_KEY_UP   )  {
		float fR = 360.0 - ( rotate_angle + 90.0f );
		now_x += 0.4f*cosf( fR * PI / 180.0f );
		now_z += 0.4f*sinf( fR * PI / 180.0f );
	}

	// move backward
	if( key == GLUT_KEY_DOWN )  {
		float fR = 360.0 - ( rotate_angle + 90.0f );
		now_x -= 0.4f*cosf( fR * PI / 180.0f );
		now_z -= 0.4f*sinf( fR * PI / 180.0f );
	}

	// rotate to left
	if( key == GLUT_KEY_LEFT )  {
		rotate_angle += 2;
	}

	// rotate to right
	if( key == GLUT_KEY_RIGHT )  {
		rotate_angle -= 2;
	}

	// check x axis boundaries
	if( now_x > W / 2 - MIN_DISTANCE
		|| now_x < -W / 2 + MIN_DISTANCE) {
		now_x = old_x;
	}

	// check y axis boundaries
	if ( now_z > L / 2 - MIN_DISTANCE 
		|| now_z < -L / 2 + MIN_DISTANCE ) {
		printf("out of boundary!\n");
		now_z = old_z;
	}

	glutPostRedisplay();
}

// Keyboard input
void keyInput(unsigned char key, int x, int y)
{
   switch(key) 
   {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

// Main
int main(int argc, char **argv) 
{
	glutInit(&argc, argv);
	glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); 
	glutInitWindowSize (WINDOW_W, WINDOW_H);
	glutInitWindowPosition (100, 0);
	glutCreateWindow ("Gallery.cpp");
	initialize();
	glutDisplayFunc(drawScene);
	glutReshapeFunc(resize);
	glutKeyboardFunc(keyInput);
	glutSpecialFunc(specialkey);
	glutMainLoop();

	return 0;
}
